package xyz.robinbingo.leetcode.a20.b05.c17;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;

public class Solution {

    // 存储有向图
    private List<Integer>[] edges;
    // 标记每个节点的状态：0=未搜索，1=搜索中，2=已完成
    private int[] visited;
    // 用数组来模拟栈，下标 0 为栈底，n-1 为栈顶
    private List<Integer> result;
    // 判断有向图中是否有环
    private boolean invalid;

    public int[] findOrder(int numCourses, int[][] prerequisites) {

        // 初始化
        edges = (List<Integer>[]) new ArrayList[numCourses];
        IntStream.range(0, numCourses).forEach(a -> edges[a] = new ArrayList<>());
        visited = new int[numCourses];
        result = new ArrayList<>(numCourses);

        Arrays.stream(prerequisites).forEach(a -> edges[a[1]].add(a[0]));

        // 每次挑选一个「未搜索」的节点，开始进行深度优先搜索
        for (int i = 0; i < numCourses && !invalid; ++i) {
            if (visited[i] == 0) {
                dfs(i);
            }
        }
        if (invalid) {
            return new int[0];
        }
        // 如果没有环，那么就有拓扑排序
        // 注意下标 0 为栈底，因此需要将数组反序输出
        Collections.reverse(result);
        return result.stream().mapToInt(Integer::intValue).toArray();
    }

    private void dfs(int u) {
        // 将节点标记为「搜索中」
        visited[u] = 1;
        // 搜索其相邻节点
        // 只要发现有环，立刻停止搜索
        for (int v : edges[u]) {
            // 如果「未搜索」那么搜索相邻节点
            if (visited[v] == 0) {
                dfs(v);
                if (invalid) {
                    return;
                }
            }
            // 如果「搜索中」说明找到了环
            else if (visited[v] == 1) {
                invalid = true;
                return;
            }
        }
        // 将节点标记为「已完成」
        visited[u] = 2;
        // 将节点入栈
        result.add(u);
    }
}